# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import annotations

import datetime as dt
import logging
import urllib.parse as urlparse
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional

import requests

if TYPE_CHECKING:
  from crossbench.runner.timing import AnyTime

DEFAULT_REQUEST_TIMEOUT = dt.timedelta(seconds=10)

RequestException = requests.RequestException
HTTPError = requests.HTTPError

Response = requests.Response


def retry_timeout_request(
    url: str, timeout: AnyTime, retry: int, verbose: bool, method: str,
    send_request: Callable[[float], requests.Response]) -> requests.Response:
  deadline = dt.datetime.now() + dt.timedelta(seconds=to_seconds(timeout))
  request_timeout_seconds = to_seconds(timeout)
  i = 0
  while True:
    try:
      if verbose:
        logging.debug("%s: url: %s", method, url)
      response = send_request(request_timeout_seconds)
      response.raise_for_status()
      return response
    except requests.RequestException as e:
      request_timeout_seconds = (deadline - dt.datetime.now()).total_seconds()
      if i < retry and request_timeout_seconds > 0:
        i += 1
        if verbose:
          logging.warning("%s request failed url=%s, retrying: %s", method, url,
                          e)
        continue
      if verbose:
        logging.error("%s request failed url=%s", method, url)
      logging.debug("RequestException: %s", e)
      raise e


def get(url: str,
        timeout: AnyTime = DEFAULT_REQUEST_TIMEOUT,
        retry: int = 0,
        verbose: bool = True) -> requests.Response:
  return retry_timeout_request(
      url, timeout, retry, verbose, "GET",
      lambda request_timeout_seconds: requests.get(
          url, timeout=request_timeout_seconds))


def post(url: str,
         body_json: Optional[Any] = None,
         headers: Optional[Mapping[str, str]] = None,
         timeout: AnyTime = DEFAULT_REQUEST_TIMEOUT,
         retry: int = 0,
         verbose: bool = True) -> requests.Response:
  return retry_timeout_request(
      url, timeout, retry, verbose, "POST",
      lambda request_timeout_seconds: requests.post(
          url, headers=headers, json=body_json, timeout=request_timeout_seconds
      ))


def to_seconds(delta: AnyTime) -> float:
  if isinstance(delta, dt.timedelta):
    return delta.total_seconds()
  return delta


def update_url_query(url: str, query_params: Mapping[str, str]) -> str:
  parsed_url = urlparse.urlparse(url)
  query = dict(urlparse.parse_qsl(parsed_url.query))
  query.update(query_params)
  parsed_url = parsed_url._replace(query=urlparse.urlencode(query, doseq=True))
  return parsed_url.geturl()


def quote(value: str) -> str:
  return urlparse.quote(value)
